Skip to content

[wip] escape analysis 2#3213

Open
aleksisch wants to merge 2 commits into
masterfrom
aleksisch/escape-analysis-2
Open

[wip] escape analysis 2#3213
aleksisch wants to merge 2 commits into
masterfrom
aleksisch/escape-analysis-2

Conversation

@aleksisch

Copy link
Copy Markdown
Collaborator

No description provided.

aleksisch and others added 2 commits June 18, 2026 16:27
Move escapeAnalysis / scopeFreeOptimization out of the macro infer
fixpoint and run them once after buildAccessFlags, where callee
sideEffectFlags are final - groundwork for gating call-argument escape
on the callee's real side effects instead of builtIn-only.

Placed before lint/foldUnsafe so the re-infer of the inserted scope_free
keeps the original ordering and does not re-trip already-folded unsafe
checks. A single dirty re-type is the fixpoint (the inserted call is a
generated terminal that creates no new candidate and changes no rws).

Behavior-preserving: isEscapeNeutralCall still gates on builtIn.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend escape analysis so a local `new`-pointer passed to a SCRIPT
function can be freed at scope exit when the callee provably does not let
that argument escape - previously only pure built-ins qualified.

New Pass 0 (ParamEscapeAnalysis): an optimistic fixpoint over each
analyzable function's by-value pointer-to-struct parameters. A parameter
is escape-free unless its body leaks the pointer (return, store, capture
into a closure, or pass to a non-neutral argument); the fixpoint lets the
property transit call chains and converge on mutual recursion. Result
lands on Variable::does_not_escape and is consumed by isArgEscapeNeutral
at call sites (built-ins still judged by declared side effects).

Also: comparison operands (==/!=) are escape-neutral, so a null-guard
`p == null` no longer counts as a leaking use - this fixes both the new
parameter pass and the existing local pass (a null-checked local is now
freeable).

Tests: pure/transitive script-call frees (heap stays flat) and soundness
(store / transitive-store / closure-capture in a script callee must NOT
free) under the validating collect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
if ( a->name==name && isParamEscapeCandidate(a) ) escaped.insert(a);
}
}
virtual void preVisit ( ExprField * expr ) override {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExprSafeField?

Visitor::preVisit(expr);
for ( auto & cap : expr->capture ) escapeByName(cap.name);
}
virtual void preVisit ( ExprMakeGenerator * expr ) override {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lambdas are also ExprMakeStruct via move semantics.

@borisbat borisbat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs more extensive testing for the cases specified. regular lambda. struct init by move. etc etc

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Extends the compiler’s escape-free optimization to treat certain script-function calls as escape-neutral on a per-parameter basis (interprocedural fixpoint), adds regression tests for both “must not free” and “should free” scenarios, and adjusts when escape analysis/scope-free are run in the compilation pipeline.

Changes:

  • Add interprocedural per-parameter escape analysis for script functions and use it to classify script call arguments as escape-neutral where proven safe.
  • Move escape analysis + scope-free optimization out of the infer loop into ast_parse.cpp after buildAccessFlags, with a targeted dirty re-infer.
  • Add GC tests covering script-call escaping and script-call non-escaping behaviors.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/gc/test_gc_escape_free.das Adds tests asserting locals passed to escaping script callees are not statically freed.
tests/gc/test_gc_escape_free_frees.das Adds tests asserting locals passed to provably pure script callees are statically freed (heap stays flat).
src/ast/ast_parse.cpp Moves escapeAnalysis/scopeFreeOptimization to the parse pipeline and performs a post-insertion dirty re-infer.
src/ast/ast_infer_type.cpp Removes escapeAnalysis/scopeFreeOptimization invocation from the infer loop.
src/ast/ast_escape_analysis.cpp Adds per-parameter fixpoint analysis for script functions and updates call-site neutrality checks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


// a SCRIPT callee that stores its argument into a global -> its parameter escapes, so a local
// passed only here must NOT be freed
def keep_node_script(var p : Node?) { g_kept = p }

// transitive: keep_node_script_t forwards p to keep_node_script, which stores it -> the escape
// transits the call chain, so the parameter still escapes
def keep_node_script_t(var p : Node?) { keep_node_script(p) }

// a script callee that captures its argument into a lambda stored in a global -> the parameter
// escapes through the capture frame
def keep_node_capture(var p : Node?) { g_lam <- @() : int { return p.x } }
Comment thread src/ast/ast_parse.cpp
Comment on lines +936 to +942
program->escapeAnalysis(logs);
if ( program->scopeFreeOptimization(logs) ) {
inferTypesDirty(program.get(), logs, true);
if ( program->failed() ) {
program->error("internal compiler error: escape free optimization infer to fail", "", "", LineInfo(), CompilationError::internal_pod_analysis_infer);
}
}
Comment on lines +483 to 489
// pass 0 first: interprocedural per-parameter escape, so pass 1 can free a local passed to a
// script function whose matching parameter provably does not escape
ParamEscapeAnalysis pe(logEscape ? &logs : nullptr);
pe.run(this);
EscapeAnalysisVisitor ev(logEscape ? &logs : nullptr);
visit(ev);
return ev.anyChanged;
@aleksisch aleksisch changed the title escape analysis 2 [wip] escape analysis 2 Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants